home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1997 February: Tool Chest / Dev.CD Feb 97 TC.toast / Sample Code / Interapplication Communication / MenuScripter 4.0 / Associated Documentation / 7Edit Read Me - Part 2 < prev    next >
Encoding:
Text File  |  1996-07-09  |  24.0 KB  |  607 lines  |  [TEXT/ttxt]

  1. Complex Specifiers
  2.  
  3. So far we have dealt only with simple specifiers. These specifiers have been formName, formAbsolutePosition and formPropertyID.
  4.  
  5. Now it's time to get onto the complex specifiers. These are formRelativePosition, formTest, formRange and formWhose. Luckily the Object Support Library will handle the formWhose for you aslong as you install a compare procedure and a count procedure with the routine AESetObjectCallbacks(), these routines are needed by formTest anyway. More on that later, first we'll get formRelativePosition and formRange out of the way.
  6.  
  7. formRelativeForm Example
  8.  
  9. 7Edit does not implement formRelativeForm for all text types, only cInsertionPoint. Feel free to implement cWord, cParagraph etc.
  10.  
  11. tell application "7Edit"
  12.     make new text at insertion point before first word ¬
  13.                      of document 1 with data "Start Here"
  14. end tell
  15.  
  16. We have seen how OSL and our code resolves up to the formRelativeForm specifier. In this case we'll get a TextToken descriptor for 'first word of document 1'. On further resolution TextElemFromTextAccessor()  calls TextFormRelativePosition() with the typeMyText descriptor.
  17.  
  18. OSErr    TextFormRelativePosition(TextToken* containerToken,
  19.           AEDesc* selectionData, DescType wantClass, AEDesc* result)
  20. {
  21.    // Declare local variables
  22.     
  23.       aTextToken.tokenWindow = containerToken->tokenWindow;
  24.  
  25.       switch (wantClass)
  26.       {
  27.             case cInsertionPoint:
  28.                   err = GetEnumeratedFromDescriptor(selectionData, &aPosition);
  29.             
  30.                switch (aPosition)
  31.                {
  32.          case kAEPrevious:
  33.                      case kAEBefore:
  34.                      case kAEBeginning:
  35.                            // No change to offset - just 0 length now
  36.                            // containerToken.tokenOffset =
  37.             // containerToken.tokenOffset;
  38.                            aTextToken.tokenOffset = containerToken->tokenOffset;
  39.                            aTextToken.tokenLength = 0;
  40.                         break;
  41.             
  42.                      case kAENext:
  43.                      case kAEAfter:
  44.                      case kAEEnd:
  45.                            aTextToken.tokenOffset = containerToken->tokenOffset +
  46.                                          containerToken->tokenLength;
  47.                            aTextToken.tokenLength = 0;
  48.                         break;
  49.                     
  50.                      default:
  51.                            err = errAEIllegalIndex;
  52.       }
  53.    break;
  54.             
  55.          default:
  56.                err = errAEWrongDataType;    // Could do cChar, cWord… but
  57.    }                                // this is only a sample
  58.     
  59.    if (noErr != err) goto done;
  60.  
  61.    err = AECreateDesc(typeMyText, (Ptr)&aTextToken,
  62.                               sizeof(aTextToken), result);
  63.  
  64. done:
  65.       return(err);
  66. }
  67.  
  68. The routine then creates a token of zero length (for an insertion point) at either end of the token depending on the type of formRelative position. Admittedly the other types of text such as cWord and cParagraph would be a little more tricky. If you want to do it have a look at some of the routines GetTextTokenObjectSpecifier() uses in 'SVAETextUtils.c'. Not only will you get a greater understanding of creating object specifiers, you'll also find some routines that would be fairly useful.
  69.  
  70.  
  71. formRange Example
  72.  
  73. Handling formRange is not too complicated in 7Edit. Consider the following script:
  74.  
  75. tell application "7Edit"
  76.     select text from character 12 thru character 18 ¬
  77.                                    of document "Untitled"
  78. end tell
  79.  
  80. The object specifier for 'text from character 12 thru character 18 of document "Untitled"' can be diagramatically presented in the way below.
  81.  
  82.  
  83.  
  84.  
  85.  
  86.  
  87.  
  88.  
  89.  
  90.  
  91.  
  92.  
  93.  
  94.  
  95.  
  96.  
  97.  
  98.  
  99.  
  100.  
  101.  
  102.  
  103.  
  104.  
  105.  
  106.  
  107.  
  108.  
  109.  
  110.  
  111.  
  112.  
  113.  
  114.  
  115.  
  116.  
  117. When we resolve this through the TextElemFromTextAccessor() the document is coerced to a TextToken descriptor as we've seen before. The routine TextFormRange() is then called through the switch statement.
  118.  
  119. OSErr    TextFormRange(TextToken* containerToken, AEDesc* selectionData,
  120.                                   DescType wantClass, AEDesc* result)
  121. {
  122.    // Declare local variables
  123.  
  124.          // coerce the selection data into an AERecord
  125.       err = AECoerceDesc(selectionData, typeAERecord,
  126.                                       &selectionRecord);
  127.       if (noErr != err) goto done;
  128.     
  129.              // get the start object as a text token this will reenter
  130.              // TextElemFromTextAccessor() but as formAbsolutePosition via 
  131.              // our installed coercion handler CoerceObjToAnything()
  132.              // because the keyAERangeStart parameter is actually an object specifier.
  133.       err = AEGetKeyPtr(&selectionRecord, keyAERangeStart, typeMyText,
  134.                                &returnedType, (Ptr)&startToken,
  135.                                  sizeof(startToken), &actualSize);
  136.    if (noErr != err) goto done;
  137.     
  138.        // now do the same for the stop object
  139.    err = AEGetKeyPtr(&selectionRecord, keyAERangeStop, typeMyText,
  140.                                &returnedType, (Ptr)&stopToken,
  141.                                  sizeof(stopToken), &actualSize);
  142.    if (noErr != err) goto done;
  143.     
  144.    if (containerToken->tokenWindow != startToken.tokenWindow
  145.                || containerToken->tokenWindow != stopToken.tokenWindow)
  146.    {
  147.       err = errAECorruptData;        // or whatever
  148.       goto done;
  149.    }
  150.         
  151.    // Use startToken to create result descriptor
  152.    startToken.tokenLength = stopToken.tokenOffset +
  153.                stopToken.tokenLength - startToken.tokenOffset;
  154.                                                          
  155.    err = AECreateDesc(typeMyText, (Ptr)&startToken,
  156.                               sizeof(startToken), result);
  157.  
  158. done:
  159.       // Clean up
  160.  
  161.       return(err);
  162. }
  163.  
  164. The routine takes the two range descriptors which are of formAbsolutePosition, and resolves them into an internal representation i.e. TextToken's. It then creates a TextToken which begins at the starting token and goes to the end of the ending token.
  165.  
  166. formTest Example
  167.  
  168. As stated before, you need to install a compare procedure and a count procedure to support formTest. This is well worth doing as it makes your application much more powerful in scripting terms. In the sample code we install these procdures in the InstallObjectCallbacks() routine which is in 'SVAECompare.c'.
  169.  
  170. err = AESetObjectCallbacks(NewOSLCompareProc(MyCompareProc),
  171.                            NewOSLCountProc(MyCountProc),
  172.                            NULL, NULL, NULL, NULL, NULL);
  173.  
  174. Here is an example script that uses these installed procedures:
  175.  
  176. tell application "7Edit"
  177.     delete every word of document 1 whose length > 5
  178. end tell
  179.  
  180. The first thing that the OSL does in this script is to resolve 'document 1'. As we've seen before this gives us a WindowToken descriptor. The OSL then calls the count procedure to count all the words in a container which is our WindowToken descriptor.
  181.  
  182. pascal OSErr MyCountProc(DescType desiredType,
  183.      DescType containerClass, const AEDesc *container, long* result)
  184. {   
  185.    // Declare local variables
  186.         
  187.    // Resolve an object specifier if necessary
  188.     
  189.    *result = -1;            // easily recognized illegal value
  190.         
  191.    switch (desiredType)
  192.    {
  193.       case cDocument:
  194.             case cWindow:
  195.          if ((containerClass == typeNull)
  196.             || (containerClass == cApplication))
  197.                         *result = CountWindows();
  198.                   else
  199.                         err = errAEWrongDataType;
  200.       break;
  201.             
  202.       case cChar:
  203.             case cWord:
  204.             case cParagraph:
  205.                   err = AECoerceDesc(&tempDesc, typeMyText, &aDesc);
  206.                   if (typeNull == aDesc.descriptorType)
  207.                         err = errAENoSuchObject;
  208.                   if (noErr != err) break;
  209.                      GetRawDataFromDescriptor(&aDesc, (Ptr)&aTextToken,
  210.                                  sizeof(aTextToken), &actualSize);
  211.  
  212.                   err = CountTextElements(TEHandleFromTextToken(&aTextToken),
  213.                      aTextToken.tokenOffset, aTextToken.tokenLength,
  214.                                          desiredType, &shortResult);
  215.                   if (noErr != err) break;
  216.                   *result = shortResult;
  217.             break;
  218.             
  219.             default:
  220.                   err = errAECantHandleClass;
  221.    }
  222.     
  223. done:
  224.    // Clean up
  225.         
  226.       return(err);
  227. }    // MyCountProc
  228.  
  229. We can see that the MyCountProc() then switches on the desired class to count, which in our case is cWord. Here our WindowToken descriptor gets coerced into a TextToken descriptor and the words in this are counted.
  230.  
  231. The OSL then goes through each word by creating a formAbsolutePosition object specifier for each and passing this along with a descriptor of the comparator and the operator to compare to our compare procedure.
  232.  
  233. pascal OSErr    MyCompareProc(DescType oper, const AEDesc* obj1,
  234.                                const AEDesc* obj2, Boolean* result)
  235. {
  236.       // Declare local variables
  237.     
  238.       err = ExtractData(obj1, &desc1);
  239.       if (err != noErr) goto done;
  240.          err = ExtractData(obj2, &desc2);
  241.       if (err != noErr) goto done;
  242.  
  243.       // Make sure the 2 data types are the same
  244.       if (desc1.descriptorType != desc2.descriptorType)
  245.       {
  246.             err = AEDuplicateDesc(&desc2, &tempDesc);
  247.             if (err != noErr) goto done;
  248.             err = AEDisposeDesc(&desc2);
  249.          err = AECoerceDesc(&tempDesc, desc1.descriptorType, &desc2);
  250.             if (err != noErr)   // If we can't coerce one way, try
  251.             {                   // the opposite way.
  252.                           // We disposed of desc2
  253.                    err = AEDuplicateDesc(&tempDesc, &desc2);
  254.                    err = AEDisposeDesc(&tempDesc);
  255.                    err = AEDuplicateDesc(&desc1, &tempDesc);
  256.                    if (err != noErr) goto done;
  257.                    err = AEDisposeDesc(&desc1);
  258.                    err = AECoerceDesc(&tempDesc, desc2.descriptorType, &desc1);
  259.                    if (err != noErr) goto done;
  260.             }
  261.    }
  262.     
  263.    // Now that we know that the 2 types are the same,
  264.    // go ahead and run the compare
  265.    switch(desc1.descriptorType)
  266.    {
  267.             case typeChar:
  268.          err = MyCompareText(oper, &desc1, &desc2, result);
  269.                break;
  270.         
  271.             case typeShortInteger:
  272.             case typeLongInteger:
  273.                   err = MyCompareInteger(oper, &desc1, &desc2, result);
  274.                break;
  275.  
  276.       case typeBoolean:
  277.          err = MyCompareBoolean(oper, &desc1, &desc2, result);
  278.       break;
  279.                 
  280.             default:
  281.       err = errAEWrongDataType;
  282.    }
  283.     
  284. done:
  285.    // Clean up
  286.     
  287.    return(err);
  288. }
  289.  
  290. The MyCompareProc() then calls ExtractData() on both descriptors to get them into a form which can be compared.
  291.  
  292. OSErr    ExtractData(const AEDesc *sourceDesc, AEDesc *theData)
  293. {
  294.    // Declare local variables
  295.     
  296.       // This routine can receive: An Object specifier,
  297.    // an object token, a property token, or
  298.       // some data (TEXT, Rectangle, etc.). It needs to
  299.    // convert whatever it's handed into data
  300.       // and return that.
  301.     
  302.       // If we don't have any data, complain
  303.       if (sourceDesc->descriptorType == typeNull
  304.                  || sourceDesc->dataHandle == NULL)
  305.             return(errAENoSuchObject);
  306.     
  307.       // If it's an object specifier, resolve into a token
  308.       if (sourceDesc->descriptorType == typeObjectSpecifier)
  309.             err = AEResolve(sourceDesc, kAEIDoMinimum, &intermediateDesc);
  310.       else    // Otherwise, just copy it
  311.             err = AEDuplicateDesc(sourceDesc, &intermediateDesc);
  312.  
  313.       if (err != noErr) goto done;
  314.     
  315.       // Now that we have a token, read from it
  316.       switch (intermediateDesc.descriptorType)
  317.       {
  318.       case typeMyApplProp:
  319.             case typeMyTextProp:
  320.             case typeMyWindowProp:
  321.             case typeMyText:
  322.                   err = HandleGetData(&intermediateDesc, theData);
  323.                break;
  324.  
  325.             default:
  326.                   // This is probably raw data , so pass it back up the line
  327.                   err = AEDuplicateDesc(&intermediateDesc, theData);
  328.    }
  329.  
  330. done:
  331.    // Clean up
  332.  
  333.       return(err);
  334. }
  335.  
  336. ExtractData() resolves any object specifiers then uses HandleGetData(), which we saw before in the Get Data Example, to get a descriptor of the actual value. If the descriptor isn't of one of our token types then we assume that it's already a descriptor of an actual value. This will be the case for the comparison descriptor in our example. It will be a descriptor holding an integer of value 5.
  337.  
  338. The OSL creates a list and adds the formAbsolutePosition object specifier of every word that MyCompareProcedure() returns true to. This list forms the result for our example script.
  339.  
  340. Recording
  341.  
  342. One of the useful things about AppleScript is the ability to record your actions. Applications like Script Editor can record the actions of your application by intercepting all the Apple Events sent to your application. This means that things like Cut and Paste from the Edit menu need to cause an Apple Event to be sent. These events need to be of a similar form to what AppleScript would send your application. This involves creating object specifiers for elements affected.
  343.  
  344. Another example, let's go through what happens when we select Cut from the Edit menu.
  345.  
  346. Cut is handled by the DoCommand() routine in 'SVEditMain.c'. This calls a routine in 'SVAERecording.c' called IssueCutCommand().
  347.  
  348. void    IssueCutCommand(DPtr theDocument)
  349. {            
  350.    DoEditCommand(theDocument, editCutCommand);
  351. }
  352.  
  353. The main business is done in DoEditCommand().
  354.  
  355. void    DoEditCommand(DPtr theDocument, editCommandType whatCommand)
  356. {
  357.    // Declare local variables
  358.     
  359.    err = SendSelectionEvent(theDocument);
  360.       if (noErr != err) goto done;
  361.  
  362.          // Now create and send the appropriate cut,
  363.    // copy, paste or clear AppleEvent
  364.     
  365.    switch (whatCommand)
  366.    {
  367.       case  editCutCommand:
  368.          theEventID = kAECut;
  369.                   theEventClass = kAEMiscStandards;
  370.                break;
  371.             
  372.       case  editCopyCommand:
  373.                   theEventID = kAECopy;
  374.          theEventClass = kAEMiscStandards;
  375.       break;
  376.  
  377.       case  editPasteCommand:
  378.          theEventID = kAEPaste;
  379.                   theEventClass = kAEMiscStandards;
  380.       break;
  381.  
  382.       case  editClearCommand:
  383.                   theEventID = kAEDelete;
  384.                   theEventClass = kAECoreSuite;
  385.       break;
  386.    }
  387.     
  388.       err = MakeSelfAddress(&ourAddress);
  389.       if (noErr != err) goto done;
  390.             
  391.       err = AECreateAppleEvent(theEventClass, theEventID,
  392.                      &ourAddress, 0, 0, &editCommandEvent);    
  393.       if (noErr != err) goto done;
  394.             
  395.          // and now Send the message
  396.       err = AESend(&editCommandEvent, &ignoreReply, kAENoReply,
  397.                    kAEHighPriority, kAEDefaultTimeout, NULL, NULL);
  398.         
  399. done:
  400.    // Clean up
  401.  
  402. } // DoEditCommand
  403.  
  404. First of all we send off an event to say what the selection is.
  405.  
  406. OSErr    SendSelectionEvent(DPtr docPtr)
  407. {
  408.       // Declare local variables
  409.  
  410.       err = MakeSelfAddress(&ourAddress);
  411.       if (noErr != err) goto done;
  412.     
  413.          // Build an object to represent the
  414.       // current document's selection
  415.          // MakeSelectedTextObj
  416.       err = MakeSelectedTextObj(docPtr->theWindow,
  417.                               docPtr->theText, &textObj);
  418.       if (noErr != err) goto done;
  419.     
  420.       err = AECreateAppleEvent(kAEMiscStandards, kAESelect,
  421.                              &ourAddress, 0, 0, &selectEvent);    
  422.       if (noErr != err) goto done;
  423.     
  424.          // add parameter
  425.       err = AEPutParamDesc(&selectEvent, keyDirectObject, &textObj);
  426.       if (noErr != err) goto done;
  427.                     
  428.          // and now send the message
  429.       err = AESend(&selectEvent, &ignoreReply, kAENoReply,
  430.              kAEHighPriority, kAEDefaultTimeout, NULL, NULL);
  431.       if (noErr != err) goto done;
  432.  
  433. done:    
  434.       // Clean up
  435.  
  436.       return(err);
  437. }
  438.  
  439. SendSelectionEvent() gets an object specifier for the current selection and sticks this into a select event and sends it to itself. DoEditCommand() then creates a cut event and also sends it to itself.
  440.  
  441. Drag Manager Support
  442.  
  443. Most of the code that deals with the Drag Manager is in 'SVDrag.c'.  Although Gestalt is used to check if the Drag Manager is available, a number of situations can occur that may cause the DragLib shared library to fail to load. Because we're weak linking to the library, we need to check we really have a connection to the shared library. This can be done quite simply by checking any symbol against the constant kUnresolvedSymbolAddress. The following code snippet shown this, and is done after Gestalt has been used in the CheckEnvironment routine.
  444.  
  445. #if GENERATINGCFM
  446.    if ( gHasDragManager )
  447.       gHasDragManager = (InstallTrackingHandler != (void*) kUnresolvedSymbolAddress);
  448. #endif
  449.  
  450. So what can cause a shared library to fail to link? Well, the most common cause is a low memory situation. Even if the code is already loaded, a shared library may still allocate a new copy of its global data section.
  451.  
  452. Everything else is fairly well known, but I'll point out a few things just in case. It's important to draw a caret at the insertion point when tracking a drag operation which includes text. It can be very confusing for the user if this is not implemented. Also, intelligent Cut and Paste applies equally well to drag and drop operation. Quite simply, this involves handling the spaces at each end of a text selection. See “Inside Macintosh: More Macintosh Toolbox”, Chapter 2 “Scrap Manager”, Page 10 “Intelligent Cut and Paste” for more information. Something to watch out for is dropping text on the original selection. This can be easily overlooked, and usually results in an unpleasant user experience. You should not draw a caret over the original selection, and zoom the dragged text back to the selection if a drop occurs. Lastly, don't forget to delete the text selection if it's dragged to the Finder's Trash.
  453.  
  454. QuickDraw GX Printing Support
  455.  
  456. What it does
  457.  
  458.     -Initializes and disposes the GX Managers
  459.     -Modifies and handles changes to the file menu
  460.     -Adds a gxJob to a 7Edit document
  461.     -Loads and saves a flattened version of the gxJob to a file
  462.     -Implements GX page setup and Print dialogs
  463.     -Implements GX print loop
  464.  
  465. It achieves much of the above by using the techniques described in the article "Adding QuickDraw GX Printing to QuickDraw Applications" by Dave Hersey (see Develop #19). I'd suggest you have a read of this to get the full story.
  466.  
  467. In addition, it shows how to:
  468.  
  469.     -Duplicate a styled text edit record
  470.     -Print a styled text edit record
  471.   -Pass job information to windows using Apple Events
  472.  
  473. It's not intended to be a definitive 'document' on how to implement these features, but illustrates one way of doing it.  Most of the code for the GX printing is in the file 'SVEditGXPrinting.c'.
  474.  
  475. How the interesting stuff works
  476.  
  477. Duplicating a styled text edit record
  478.  
  479. This is done by "DuplicateStyleTERec":
  480.  
  481.     void DuplicateStyleTERec( TEHandle  hSourceTE,
  482.                               TEHandle *hDestTE,
  483.                               Rect     *destRect,
  484.                               GrafPtr   destPort )
  485.  
  486. hSourceTE  Handle to a TERec which contains the text edit you want to copy.
  487. hDestTE    Handle returned containing the duplicated TERec.
  488. destRect   Rect which is used for the duplicates view and dest Rects. 
  489. destPort   The port that the duplicated text edit will be in. The reason a port is passed is that you may wish to put the text edit in a different window.
  490.  
  491. First we set up the port to "destPort", then we use "TEStyleNew" to create a new styled TERec.
  492.  
  493.       GetPort(&oldPort);
  494.       SetPort(destPort);
  495.     
  496.       *hDestTE = TEStyleNew(destRect, destRect);
  497.  
  498. Next we must preserve the selected range of the source text edit record and the set the range selection range to include the entirety of the text.
  499.  
  500.       oldSelStart = (*hSourceTE)->selStart;
  501.       oldSelEnd   = (*hSourceTE)->selEnd;
  502.       TESetSelect(0,(*hSourceTE)->teLength, hSourceTE);
  503.  
  504. Now we can use "GetStylScrap" to get the style of the current selection - which is now the whole text. This being done we can reset the selection range.
  505.  
  506.       printerTextStyles = GetStylScrap(hSourceTE);
  507.  
  508.       TESetSelect(oldSelStart, oldSelEnd, hSourceTE);
  509.  
  510. "TEStyleInsert" is used to insert the text and acquired style from the source into the destination text edit (remembering to lock the source's text before trying to insert it). 
  511.  
  512.       HLock((Handle)((*hSourceTE)->hText));
  513.       TEStyleInsert ( (Ptr)*((*hSourceTE)->hText),
  514.                       (*hSourceTE)->teLength,
  515.                       printerTextStyles,
  516.                       *hDestTE);        
  517.       HUnlock((Handle)((*hSourceTE)->hText));
  518.  
  519. How to print a styled text edit record with QuickDraw GX
  520.  
  521. This is done in the function "GXPrintLoop". It uses a modified version of the print loop from Develop 19.  It creates a invisible window to draw the text edit record into. This is because "TEUpdate" is used to do the drawing and it will not only draw into the printer port, but will also draw in it's own port. Therefore we need to create an 'invisible' port to draw into. So we give the window an empty (0,0,0,0) rect for bounds and we set the visible flag to false.
  522.  
  523.       imagingWind = NewWindow ( nil, &tempWindRect, "\p",
  524.                                 false, documentProc,
  525.                                 (WindowPtr)-1, false, 0);
  526.  
  527.       SetPort((GrafPtr)imagingWind);
  528.  
  529. Now we duplicate the Text Edit record in the document, to a temporary record. Then we use "GetPageEnds" to calculate how many pages are needed and where each page ends.
  530.  
  531.       DuplicateStyleTERec( theDoc->theText, &tempTE, &printerPage, (GrafPtr)imagingWind );
  532.  
  533.       GetPageEnds( printerPage.bottom-printerPage.top,
  534.                    tempTE,
  535.                    pageEnds,
  536.                    &numDocPages);
  537.  
  538. Next it puts up the progress dialog then it steps through the pages. At each page, it  clips the text edit rec so it only draw lines of text on the page. It then uses TEUpdate to draw each page.
  539.  
  540.       rectToClip = printerPage;
  541.       if (pageCounter == 1)
  542.         rectToClip.bottom = rectToClip.top + pageEnds[pageCounter-1];
  543.       else
  544.         rectToClip.bottom = rectToClip.top + pageEnds[pageCounter-1]
  545.                                            - pageEnds[pageCounter-2];
  546.       ClipRect(&rectToClip);
  547.  
  548.       TEUpdate(&printerPage, tempTE);
  549.  
  550. Now we can scroll the text edit rec ready for the next page.
  551.  
  552.       if (pageCounter < lastPage)
  553.         TEScroll(0,rectToClip.top-rectToClip.bottom, tempTE);
  554.  
  555. Once we've printed all the pages we finish the job and dispose of any structures we've created.
  556.      
  557. How to pass job information to windows using Apple Events
  558.  
  559. The GX printing code passes the job to and from the window using the Set/GetWindowProperty functions. It declares a new token property:
  560.  
  561.       #define     pGXPageSetup    'TFGX'
  562.  
  563. The GXJob is then passed in a flattened state using "GXFlattenJobToHdl()" to flatten it down to a handle, or "GXUnflattenJobFromHdl()" to extract it again.
  564.  
  565. 7Edit Background and Changes
  566.  
  567. 7Edit was originally written when the Object Support Library was still in it's early stages. This update hopefully makes the flow of events easier to follow, even if this means not doing things in the most clever way. Each Apple Event handler is now in it's own file, except for those in the Required Suite which are still in 'SVAppleEvents.c'. So if you want to look at the delete handler look in 'SVAEDelete.c'. The accessor routines have been moved into the 'SVAEAccessors.c' file and the coercion routines to 'SVAECoercions.c'. Alot of the extra routines that help the event handlers deal with windows and text have been moved to 'SVAEWindowUtils.c' and 'SVAETextUtils.c' respectively.
  568.  
  569. Support for the Edition Manager has been taken out. The code had some bugs in it, and wasn't a major part of 7Edit. However, 7Edit now includes support for QuickDraw GX printing and the Drag Manager.
  570.  
  571. We also removed and added items to make 7Edit closer to the Scriptable Text Editor. This included the line class which is in the Text Suite.
  572.  
  573. AppleEvents and C++
  574.  
  575. If you are thinking of writing a scriptable application it may be worth your while looking at Metrowerk's PowerPlant and the latest version of MacApp 3.3. Both of these frameworks support scripting.
  576.  
  577. Known Problems
  578.  
  579. You may find that your machine will crash if you don't have a printer selected through the Chooser.
  580.  
  581. Further Things That Could Be Done
  582.  
  583. • Even though 7Edit supports drag and drop it is as yet unrecordable. This requires some thought because it has to be to handle dragging to and from other applications, including the Finder.
  584. • The Scriptable Text Editor allows you to specify text item delimiters. This allows you to say make the delimiter a fullstop hence giving the ability to pick up sentences.
  585. • 7Edit does not support the full Text Suite,  this could be done.
  586. • Tidying up more of the older code.
  587.  
  588.  
  589. The way we have implemented the scripting ability in 7Edit is not necessarily the best way. This example is here to give you an idea of what is needed to write a scriptable application. There are bound to be better ways, or ways that are more suited to your application.
  590.  
  591. Happy scripting.
  592.  
  593. Greg Sutton
  594. Developer Technical Support
  595. November '95
  596.  
  597. © 1995 by Apple Computer, Inc. All rights reserved.
  598.  
  599. References
  600.  
  601. develop Issue 10  pg 8-32   'Apple Event Objects and You'
  602.  
  603. develop Issue 21  pg 48-72 'Designing a Scripting Implementation'
  604.  
  605. develop Issue 22  pg 81-82 'According to Script - Scripting Quandries'
  606.  
  607.